package com.charon.auth.demo.controller;

import com.charon.auth.demo.entity.User;

import com.charon.auth.properties.SecurityProperties;
import com.charon.common.aspect.ControllerLogAspect;
import com.charon.common.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
  *@ClassName: UserController
  *@Description: 测试用户Security
  *@Author: Charon [248135724@qq.com]
  *@Date: 2019/1/12 0012 15:09
  *@Version: 1.0
  */
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @ModelAttribute
    public void before(HttpServletRequest request){
        log.info(request.getRequestURI());
    }

    @ApiOperation(value="获取用户列表", notes="获取所有用户列表",produces = "application/json")
    @JsonView(User.UserSimpleView.class)
    //Pageable是spring-data中的分页类
    @GetMapping
    public List<User> query(@RequestParam(required = false)String userName
            ,@RequestParam(required = false) @PageableDefault(page = 2,size = 2,sort = "age,asc") Pageable pageable
            ,@RequestParam(required = false) User u){
        if(pageable!=null){
            log.info("Query："+pageable.toString());
        }
        List<User> list=new ArrayList<>();
        User user = new User();
        user.setId("1");
        user.setPassword("123");
        list.add(user);
        list.add(u);
        list.add(user);
        return list;
    }

    @ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息",produces = "application/json")
    // ApiResponses 增加返回结果的描述
    @ApiResponses(value = {@ApiResponse(code = 405,message = "Invalid input",response = Integer.class)})
    @ApiImplicitParam(name = "id",value = "用户ID",dataType = "int",paramType = "path")
    /**
     * @ApiImplicitParams({
     *         @ApiImplicitParam(name = "id",value = "用户ID",paramType = "path",dataType = "int"),
     *         @ApiImplicitParam(name = "userName",value = "用户名称",paramType = "form",dataType = "string")
     * })
     * 使用ApiImplicitParams描述多个参数
     * (2) 使用ApiImplicitParam时，需要指定paramType,这样也便于swagger ui 生成参数的输入格式。
     * paramType 有五个可选值 ： path, query, body, header, form
     */
    //正则表达式：\\d+ 让Id只能接收数字
    @GetMapping("/{id:\\d+}")
    @JsonView(User.UserDetailView.class)
    //@PathVariable的作用是将url中的参数取到参数上。
    public User getInfo(@PathVariable("id") String id){
        User user=new User();
        user.setUserName("sun");
        user.setPassword("mercury");
        log.info("Info："+id);
        return user;
    }

    @PostMapping
    @ResponseBody
    /**
     * @NotEmpty 用在集合类上面
     * @NotBlank 用在String上面
     * @NotNull 用在基本类型上
     * 需要结合@Valid使用
     * BindingResult 和@Valid结合使用，在不符合@Valid的校验时，可以通过BindingResult进入方法，打印错误
     */
    //加了@RequestBody才能将Json解析到实体类
    public User create(@RequestBody @Valid User user, BindingResult errors){
        //当有参数格式有错误时
        if(errors.hasErrors()){
            errors.getAllErrors().stream().forEach(objectError -> log.info(objectError.getDefaultMessage()));
        }

        log.info("Create："+user.toString());
        log.info(user.getBirthday().toString());
        user.setId("1");
        return user;
    }

    @PutMapping("/{id:\\d+}")
    @JsonView(User.UserDetailView.class)
    //@PathVariable的作用是将url中的参数取到参数上。如果不是用BindingResult，
    // 那么在验证不通过的时候，@Valid将直接返回400,并不会进入到方法中去，
    // 这个时候只有400状态码，是不足以表明问题的。
    public User updateInfo(@Valid @RequestBody User user,BindingResult errors){
        if(errors.hasErrors()){
            errors.getAllErrors().forEach(error-> log.info(error.toString()));
            errors.getAllErrors().forEach(error-> log.info(error.getDefaultMessage()));
        }
        user.setUserName("sun");
        user.setPassword("mercury");
        log.info("Info："+user.getId());
        return user;
    }

    @DeleteMapping("/{id:\\d+}")
    @JsonView(User.UserSimpleView.class)
    public void delete(@PathVariable String id){
        log.info(id);
    }


    /**
     * 在执行请求进入，进行认证前，会将session放入到该缓存中
     */
    private RequestCache requestCache=new HttpSessionRequestCache();

    /**
     * 重定向使用
     */
    private RedirectStrategy redirectStrategy=new DefaultRedirectStrategy();

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 当需要身份认证的时候跳转到这。
     * @return
     */
    @GetMapping("login")
    //如果走了重定向，将不会走此处设置的状态码
    @ResponseStatus(code=HttpStatus.UNAUTHORIZED)
    public String login(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest=requestCache.getRequest(request,response);
        log.info("请求地址："+request.getRequestURI());
        String loginPage = securityProperties.getBrowser().getLoginPage();
        if(savedRequest!=null){
            String target=savedRequest.getRedirectUrl();
            log.info("引发跳转的请求是："+target);
            if(StringUtils.endsWithIgnoreCase(target,".html")){
                log.info("配置页面："+loginPage);
                //下面第三个参数可以配，是重定向的页面，如果程序走到了这里，将会直接跳转走。
                redirectStrategy.sendRedirect(request,response,loginPage);
                //不会继续执行下面的代码。
            }
        }
        return "/500.html";
    }

    /**
     * 注意，此处如果和security中的某个链接相同，那么将执行security中的链接，而不是此处，
     *      例如配置的表单登录提交的链接loginProcessingUrl("/user/form")。
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("form")
    public String success(String username,String password){
        log.info("当前：UserController.form："+username);
        log.info("密码："+password);
        return "登录成功！";
    }

    /**
     * @Description 获取当前登录成功的对象
     * @Author Charon [248135724@qq.com]
     * @Date 2019/1/20 19:00
     * @Param []
     * @return org.springframework.security.core.Authentication
     */
    @GetMapping("me")
    public Authentication getCurrentAuthentication(){
        /**
         * 源码中，在登录成功后，回调AuthenticationSuccessHandler的实现类之前，有一行源码是
         * SecurityContextHolder.getContext().getAuthentication();
         * 作用：Security将登录成功的信息存储。
         * SecurityContextHolder是一个工具类，该类中有一个常量是strategy，strategy是一个ThreadLocal对象。
         * SecurityContext是一个接口，SecurityContextImpl类实现了该接口，SecurityContextImpl是对Authentication（登录成功的对象）的一个包装，
         * 因此可以简单的理解成SecurityContext是当前登录的用户信息，而SecurityContextHolder是一个ThreadLocal。
         * 这样我们可以使用该方式拿到当前登录的对象。
         */

        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * @Description 获取当前用户信息
     * @Author Charon [248135724@qq.com]
     * @Date 2019/1/20 19:00
     * @Param UserDetails：我们在验证时创建的那个对象
     * @return org.springframework.security.core.userdetails.UserDetails
     */
    @GetMapping("userInfo")
    public UserDetails getUserInfo(@AuthenticationPrincipal UserDetails userDetails){
        return userDetails;
    }
}
